# -*- coding: utf-8 -
# Copyright (C) 2007-2008 Christopher Lenz <cmlenz@gmx.de>
# Copyright 2008 by Benoît Chesneau <benoitc@e-engura.com>
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from couchdb_django.middleware import local_
from couchdb_django import signals
from couchdb_django.schema.fields import Field

__all__ = ['Schema', 'Document']

class SchemaMeta(type):
    """
    Metaclass for all schema
    """
    def __new__(cls, name, bases, d):
        fields = {}
        for base in bases:
            if hasattr(base, '_fields'):
                fields.update(base._fields)
        for field_name, obj in d.items():
            if isinstance(obj, Field):
                print field_name
                if not obj.name:
                    obj.name = field_name
                fields[field_name] = obj

        d['_fields'] = fields
        return type.__new__(cls, name, bases, d)
        

class Schema(object):
    __metaclass__ = SchemaMeta
    
    def __init__(self, **values):
        self._data = {}
        for attrname, field in self._fields.items():
            if attrname in values:
                setattr(self, attrname, values.pop(attrname))
            else:
                setattr(self, attrname, getattr(self, attrname))

    def __repr__(self):
        return smart_str(u'<%s: %s>' % (self.__class__.__name__, unicode(self)))
    
    def __str__(self):
        if hasattr(self, '__unicode__'):
            return force_unicode(self).encode('utf-8')
        return '%s object' % self.__class__.__name__
    
    def __eq__(self, other):
        return isinstance(other, self.__class__) and self._data['_id'] == other._data['_id']
        
    def __ne__(self, other):
        return not self.__eq__(other)
        
    def __iter__(self):
        return iter(self._data)
        
    def __len__(self):
        return len(self._data or ())

    def __delitem__(self, name):
        del self._data[name]

    def __getitem__(self, name):
        return self._data[name]

    def __setitem__(self, name, value):
        self._data[name] = value
   
    def _wrap(cls, data):
        instance = cls()
        instance._data = data
        return instance
    _wrap = classmethod(_wrap)
    
    def _unwrap(self):
        return self._data

    def build(cls, **d):
        fields = {}
        for attrname, attrval in d.items():
            if not attrval.name:
                attrval.name = attrname
            fields[attrname] = attrval
        d['_fields'] = fields
        return type('AnonymousStruct', (cls,), d)
    build = classmethod(build)
        
class Document(Schema):
    def __init__(self, id=None, **values):
        signals.couchdb_pre_init.send(sender=self.__class__, values=values)
        Schema.__init__(self, **values)
        if id is not None:
            self.id = id
        signals.couchdb_post_init.send(sender=self.__class__, instance=self)
        
    def _get_id(self):
        if hasattr(self._data, 'id'):
            return self._data.id
        return self._data.get('_id')
        
    def _set_id(self, value):
        if self.id is not None:
            raise AttributeError('id can only be set on new documents')
        self._data['_id'] = value
    id = property(_get_id, _set_id)
            
    def load(cls, docid):
        print "id %s" % docid
        doc = local_.db[docid]
        if doc is None:
            return None
        return cls._wrap(doc)
    load = classmethod(load)
    
    def store(self):
        self.store_base()
    save = store
        
    def store_base(self):
        signals.couchdb_pre_save.send(sender=self.__class__, instance=self, raw=self._data)
        if self.id is None:
            docid = local_.db.create(self._data)
            self._data = local_.db.get(docid)
        else:
            db[self.id] = self._data
        signals.couchdb_post_save.send(sender=self.__class__, instance=self, raw=self._data)
        return self
    
    def delete(self):
        signals.couchdb_pre_delete.send(sender=self.__class__, instance=self, raw=self._data)
        if self.id is not None:
            del db[self.id]
        self._data = {}
        signals.couchdb_post_delete.send(sender=self.__class__, instance=self, raw=self._data)
        
    def query(cls, map_fun, reduce_fun, language='javascript',
              eager=False, **options):
        """Execute a CouchDB temporary view and map the result values back to
        objects of this schema.
        
        Note that by default, any properties of the document that are not
        included in the values of the view will be treated as if they were
        missing from the document. If you'd rather want to load the full
        document for every row, set the `eager` option to `True`, but note that
        this will initiate a new HTTP request for every document.
        """
        def _wrapper(row):
            if eager:
                return cls.load(row.id)
            data = row.value
            data['_id'] = row.id
            return cls._wrap(data)
        return local_.db.query(map_fun, reduce_fun=reduce_fun, language=language,
                        wrapper=_wrapper, **options)
    query = classmethod(query)

    def view(cls, viewname, eager=False, **options):
        """Execute a CouchDB named view and map the result values back to
        objects of this schema.
        
        Note that by default, any properties of the document that are not
        included in the values of the view will be treated as if they were
        missing from the document. If you'd rather want to load the full
        document for every row, set the `eager` option to `True`, but note that
        this will initiate a new HTTP request for every document.
        """
        def _wrapper(row):
            if eager:
                return cls.load(row.id)
            data = row.value
            data['_id'] = row.id
            return cls._wrap(data)
        return local_.db.view(viewname, wrapper=_wrapper, **options)
    view = classmethod(view)
        
    def _to_python(self, value):
        return self._wrap(data)
            
    def _to_json(self, value):
        return self._unwrap
        
        
